#include "Precompile.h"
#include "DirectInputSystem.h"

namespace Input{
	DirectInputSystem::DirectInputSystem():
		m_inputDevice(NULL),m_keyboardDevice(NULL),m_mouseDevice(NULL){
		
		// Create translation map
		m_keyTranslateMap[DIK_ESCAPE] = EIK_ESCAPE;
		m_keyTranslateMap[DIK_1] = EIK_1;
		m_keyTranslateMap[DIK_2] = EIK_2;
		m_keyTranslateMap[DIK_3] = EIK_3;
		m_keyTranslateMap[DIK_4] = EIK_4;
		m_keyTranslateMap[DIK_5] = EIK_5;
		m_keyTranslateMap[DIK_6] = EIK_6;
		m_keyTranslateMap[DIK_7] = EIK_7;
		m_keyTranslateMap[DIK_8] = EIK_8;
		m_keyTranslateMap[DIK_9] = EIK_9;
		m_keyTranslateMap[DIK_0] = EIK_0;
		m_keyTranslateMap[DIK_MINUS] = EIK_MINUS;
		m_keyTranslateMap[DIK_EQUALS] = EIK_EQUALS;
		m_keyTranslateMap[DIK_BACK] = EIK_BACK;
		m_keyTranslateMap[DIK_TAB] = EIK_TAB;
		m_keyTranslateMap[DIK_Q] = EIK_Q;
		m_keyTranslateMap[DIK_W] = EIK_W;
		m_keyTranslateMap[DIK_E] = EIK_E;
		m_keyTranslateMap[DIK_R] = EIK_R;
		m_keyTranslateMap[DIK_T] = EIK_T;
		m_keyTranslateMap[DIK_Y] = EIK_Y;
		m_keyTranslateMap[DIK_U] = EIK_U;
		m_keyTranslateMap[DIK_I] = EIK_I;
		m_keyTranslateMap[DIK_O] = EIK_O;
		m_keyTranslateMap[DIK_P] = EIK_P;
		m_keyTranslateMap[DIK_LBRACKET] = EIK_LBRACKET;
		m_keyTranslateMap[DIK_RBRACKET] = EIK_RBRACKET;
		m_keyTranslateMap[DIK_RETURN] = EIK_RETURN;
		m_keyTranslateMap[DIK_LCONTROL] = EIK_LCONTROL;
		m_keyTranslateMap[DIK_A] = EIK_A;
		m_keyTranslateMap[DIK_S] = EIK_S;
		m_keyTranslateMap[DIK_D] = EIK_D;
		m_keyTranslateMap[DIK_F] = EIK_F;
		m_keyTranslateMap[DIK_G] = EIK_G;
		m_keyTranslateMap[DIK_H] = EIK_H;
		m_keyTranslateMap[DIK_J] = EIK_J;
		m_keyTranslateMap[DIK_K] = EIK_K;
		m_keyTranslateMap[DIK_L] = EIK_L;
		m_keyTranslateMap[DIK_SEMICOLON] = EIK_SEMICOLON;
		m_keyTranslateMap[DIK_APOSTROPHE] = EIK_APOSTROPHE;
		m_keyTranslateMap[DIK_GRAVE] = EIK_GRAVE;
		m_keyTranslateMap[DIK_LSHIFT] = EIK_LSHIFT;
		m_keyTranslateMap[DIK_BACKSLASH] = EIK_BACKSLASH;
		m_keyTranslateMap[DIK_Z] = EIK_Z;
		m_keyTranslateMap[DIK_X] = EIK_X;
		m_keyTranslateMap[DIK_C] = EIK_C;
		m_keyTranslateMap[DIK_V] = EIK_V;
		m_keyTranslateMap[DIK_B] = EIK_B;
		m_keyTranslateMap[DIK_N] = EIK_N;
		m_keyTranslateMap[DIK_M] = EIK_M;
		m_keyTranslateMap[DIK_COMMA] = EIK_COMMA;
		m_keyTranslateMap[DIK_PERIOD] = EIK_PERIOD;
		m_keyTranslateMap[DIK_SLASH] = EIK_SLASH;
		m_keyTranslateMap[DIK_RSHIFT] = EIK_RSHIFT;
		m_keyTranslateMap[DIK_MULTIPLY] = EIK_MULTIPLY;
		m_keyTranslateMap[DIK_LMENU] = EIK_LMENU;
		m_keyTranslateMap[DIK_SPACE] = EIK_SPACE;
		m_keyTranslateMap[DIK_CAPITAL] = EIK_CAPITAL;
		m_keyTranslateMap[DIK_F1] = EIK_F1;
		m_keyTranslateMap[DIK_F2] = EIK_F2;
		m_keyTranslateMap[DIK_F3] = EIK_F3;
		m_keyTranslateMap[DIK_F4] = EIK_F4;
		m_keyTranslateMap[DIK_F5] = EIK_F5;
		m_keyTranslateMap[DIK_F6] = EIK_F6;
		m_keyTranslateMap[DIK_F7] = EIK_F7;
		m_keyTranslateMap[DIK_F8] = EIK_F8;
		m_keyTranslateMap[DIK_F9] = EIK_F9;
		m_keyTranslateMap[DIK_F10] = EIK_F10;
		m_keyTranslateMap[DIK_NUMLOCK] = EIK_NUMLOCK;
		m_keyTranslateMap[DIK_SCROLL] = EIK_SCROLL;
		m_keyTranslateMap[DIK_NUMPAD7] = EIK_NUMPAD7;
		m_keyTranslateMap[DIK_NUMPAD8] = EIK_NUMPAD8;
		m_keyTranslateMap[DIK_NUMPAD9] = EIK_NUMPAD9;
		m_keyTranslateMap[DIK_SUBTRACT] = EIK_SUBTRACT;
		m_keyTranslateMap[DIK_NUMPAD4] = EIK_NUMPAD4;
		m_keyTranslateMap[DIK_NUMPAD5] = EIK_NUMPAD5;
		m_keyTranslateMap[DIK_NUMPAD6] = EIK_NUMPAD6;
		m_keyTranslateMap[DIK_ADD] = EIK_ADD;
		m_keyTranslateMap[DIK_NUMPAD1] = EIK_NUMPAD1;
		m_keyTranslateMap[DIK_NUMPAD2] = EIK_NUMPAD2;
		m_keyTranslateMap[DIK_NUMPAD3] = EIK_NUMPAD3;
		m_keyTranslateMap[DIK_NUMPAD0] = EIK_NUMPAD0;
		m_keyTranslateMap[DIK_DECIMAL] = EIK_DECIMAL;
		m_keyTranslateMap[DIK_OEM_102] = EIK_OEM_102;
		m_keyTranslateMap[DIK_F11] = EIK_F11;
		m_keyTranslateMap[DIK_F12] = EIK_F12;
		m_keyTranslateMap[DIK_F13] = EIK_F13;
		m_keyTranslateMap[DIK_F14] = EIK_F14;
		m_keyTranslateMap[DIK_F15] = EIK_F15;
		m_keyTranslateMap[DIK_KANA] = EIK_KANA;
		m_keyTranslateMap[DIK_ABNT_C1] = EIK_ABNT_C1;
		m_keyTranslateMap[DIK_CONVERT] = EIK_CONVERT;
		m_keyTranslateMap[DIK_NOCONVERT] = EIK_NOCONVERT;
		m_keyTranslateMap[DIK_YEN] = EIK_YEN;
		m_keyTranslateMap[DIK_ABNT_C2] = EIK_ABNT_C2;
		m_keyTranslateMap[DIK_NUMPADEQUALS] = EIK_NUMPADEQUALS;
		m_keyTranslateMap[DIK_PREVTRACK] = EIK_PREVTRACK;
		m_keyTranslateMap[DIK_AT] = EIK_AT;
		m_keyTranslateMap[DIK_COLON] = EIK_COLON;
		m_keyTranslateMap[DIK_UNDERLINE] = EIK_UNDERLINE;
		m_keyTranslateMap[DIK_KANJI] = EIK_KANJI;
		m_keyTranslateMap[DIK_STOP] = EIK_STOP;
		m_keyTranslateMap[DIK_AX] = EIK_AX;
		m_keyTranslateMap[DIK_UNLABELED] = EIK_UNLABELED;
		m_keyTranslateMap[DIK_NEXTTRACK] = EIK_NEXTTRACK;
		m_keyTranslateMap[DIK_NUMPADENTER] = EIK_NUMPADENTER;
		m_keyTranslateMap[DIK_RCONTROL] = EIK_RCONTROL;
		m_keyTranslateMap[DIK_MUTE] = EIK_MUTE;
		m_keyTranslateMap[DIK_CALCULATOR] = EIK_CALCULATOR;
		m_keyTranslateMap[DIK_PLAYPAUSE] = EIK_PLAYPAUSE;
		m_keyTranslateMap[DIK_MEDIASTOP] = EIK_MEDIASTOP;
		m_keyTranslateMap[DIK_VOLUMEDOWN] = EIK_VOLUMEDOWN;
		m_keyTranslateMap[DIK_VOLUMEUP] = EIK_VOLUMEUP;
		m_keyTranslateMap[DIK_WEBHOME] = EIK_WEBHOME;
		m_keyTranslateMap[DIK_NUMPADCOMMA] = EIK_NUMPADCOMMA;
		m_keyTranslateMap[DIK_DIVIDE] = EIK_DIVIDE;
		m_keyTranslateMap[DIK_SYSRQ] = EIK_SYSRQ;
		m_keyTranslateMap[DIK_RMENU] = EIK_RMENU;
		m_keyTranslateMap[DIK_PAUSE] = EIK_PAUSE;
		m_keyTranslateMap[DIK_HOME] = EIK_HOME;
		m_keyTranslateMap[DIK_UP] = EIK_UP;
		m_keyTranslateMap[DIK_PRIOR] = EIK_PRIOR;
		m_keyTranslateMap[DIK_LEFT] = EIK_LEFT;
		m_keyTranslateMap[DIK_RIGHT] = EIK_RIGHT;
		m_keyTranslateMap[DIK_END] = EIK_END;
		m_keyTranslateMap[DIK_DOWN] = EIK_DOWN;
		m_keyTranslateMap[DIK_NEXT] = EIK_NEXT;
		m_keyTranslateMap[DIK_INSERT] = EIK_INSERT;
		m_keyTranslateMap[DIK_DELETE] = EIK_DELETE;
		m_keyTranslateMap[DIK_LWIN] = EIK_LWIN;
		m_keyTranslateMap[DIK_RWIN] = EIK_RWIN;
		m_keyTranslateMap[DIK_APPS] = EIK_APPS;
		m_keyTranslateMap[DIK_POWER] = EIK_POWER;
		m_keyTranslateMap[DIK_SLEEP] = EIK_SLEEP;
		m_keyTranslateMap[DIK_WAKE] = EIK_WAKE;
		m_keyTranslateMap[DIK_WEBSEARCH] = EIK_WEBSEARCH;
		m_keyTranslateMap[DIK_WEBFAVORITES] = EIK_WEBFAVORITES;
		m_keyTranslateMap[DIK_WEBREFRESH] = EIK_WEBREFRESH;
		m_keyTranslateMap[DIK_WEBSTOP] = EIK_WEBSTOP;
		m_keyTranslateMap[DIK_WEBFORWARD] = EIK_WEBFORWARD;
		m_keyTranslateMap[DIK_WEBBACK] = EIK_WEBBACK;
		m_keyTranslateMap[DIK_MYCOMPUTER] = EIK_MYCOMPUTER;
		m_keyTranslateMap[DIK_MAIL] = EIK_MAIL;
		m_keyTranslateMap[DIK_MEDIASELECT] = EIK_MEDIASELECT;

		m_keyTranslateMap[EIMOUSEBUTTON_0] = EIMOUSEBUTTON_0;
		m_keyTranslateMap[EIMOUSEBUTTON_1] = EIMOUSEBUTTON_1;
		m_keyTranslateMap[EIMOUSEBUTTON_2] = EIMOUSEBUTTON_2;
		m_keyTranslateMap[EIMOUSEBUTTON_3] = EIMOUSEBUTTON_3;
		m_keyTranslateMap[EIMOUSEBUTTON_4] = EIMOUSEBUTTON_4;
		m_keyTranslateMap[EIMOUSEBUTTON_5] = EIMOUSEBUTTON_5;
		m_keyTranslateMap[EIMOUSEBUTTON_6] = EIMOUSEBUTTON_6;
		m_keyTranslateMap[EIMOUSEBUTTON_7] = EIMOUSEBUTTON_7;

		m_lastX = m_lastY = m_lastZ = 0;
	}

	DirectInputSystem::~DirectInputSystem(){
		cleanup();
	}

	void DirectInputSystem::initialize(boost::shared_ptr<InputInitParams> inputParams){
		boost::shared_ptr<DirectInputInitParams> params = boost::static_pointer_cast<DirectInputInitParams>(inputParams);

		if(FAILED(DirectInput8Create(params->m_hinst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_inputDevice, NULL))) { 
			// DirectInput not available; take appropriate action
			LOG_ERROR("DirectInputSystem::initialize - Failed to create input device.");
			cleanup();
			assert(0);
			return;
		}

		// Set up buffering
		DIPROPDWORD dipdw;
	    // header
	    dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
	    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
	    dipdw.diph.dwObj        = 0;
	    dipdw.diph.dwHow        = DIPH_DEVICE;
	    // data
	    dipdw.dwData            = 16;

		// Initialize keyboard support
		{
			// Create keyboard device
			if(FAILED(m_inputDevice->CreateDevice(GUID_SysKeyboard, &m_keyboardDevice, NULL))){
				LOG_ERROR("DirectInputSystem::initialize - Failed to create keyboard device.");
			    cleanup();
				assert(0);
				return;
			}

			if(FAILED(m_keyboardDevice->SetCooperativeLevel(params->m_hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE))){
				LOG_ERROR("DirectInputSystem::initialize - Failed to set keyboard cooperative level.");
			    cleanup();
				assert(0);
			    return;
			}

			if(FAILED(m_keyboardDevice->SetDataFormat(&c_dfDIKeyboard))){
				LOG_ERROR("DirectInputSystem::initialize - Failed to set keyboard device data format.");
			    cleanup();
				assert(0);
			    return;
			}

			if(FAILED(m_keyboardDevice->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph))) {
				LOG_ERROR("DirectInputSystem::initialize - Failed to set keyboard device buffer.");
			    cleanup();
				assert(0);
			    return;
			}

			if(m_keyboardDevice)
				m_keyboardDevice->Acquire();
		}

		// Initialize mouse support
		{
			// Create mouse device
			if(FAILED(m_inputDevice->CreateDevice(GUID_SysMouse, &m_mouseDevice, NULL))){
				LOG_ERROR("DirectInputSystem::initialize - Failed to create mouse device.");
			    cleanup();
				assert(0);
			    return;
			}

			if(FAILED(m_mouseDevice->SetCooperativeLevel(params->m_hwnd,DISCL_EXCLUSIVE | DISCL_FOREGROUND))){
				LOG_ERROR("DirectInputSystem::initialize - Failed to set mouse device cooperative level.");
			    cleanup();
				assert(0);
			    return;
			}

			if (FAILED(m_mouseDevice->SetDataFormat(&c_dfDIMouse))){
				LOG_ERROR("DirectInputSystem::initialize - Failed to set mouse device data format.");
			    cleanup();
				assert(0);
			    return;
			}

			if(FAILED(m_mouseDevice->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph))) {
				LOG_ERROR("DirectInputSystem::initialize - Failed to set mouse device buffer.");
			    cleanup();
				assert(0);
			    return;
			}

			if(m_mouseDevice)
				m_mouseDevice->Acquire();
		}
	}

	void DirectInputSystem::update(){
		m_bufferSwapMutex->lock();
		HRESULT hr;

		DIDEVICEOBJECTDATA bufferedData;
		DWORD buffSize = 1;

		// Check keyboard buffer
		while(true){
			buffSize = 1;
			hr = m_keyboardDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),&bufferedData,&buffSize,0);

			if(FAILED(hr)){
				m_keyboardDevice->Acquire();
	            break;
	        }

			if(buffSize == 0){
            	break;
        	}

			// Check retrieved data
			processKeyboardInput(bufferedData);
		}

		// Check mouse buffer
		while(true){
			buffSize = 1;
			hr = m_mouseDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),&bufferedData,&buffSize,0);

			if(FAILED(hr)){
				m_mouseDevice->Acquire();
				break;
	        }

			if(buffSize == 0){
            	break;
        	}

			// Check retrieved data
			processMouseInput(bufferedData);
		}

		m_synced = false;
		m_bufferSwapMutex->unlock();
	}

	void DirectInputSystem::processMouseInput(DIDEVICEOBJECTDATA &data){
		switch(data.dwOfs){
		case DIMOFS_X:
			//m_mouseDataBackPtr->xPos = data.dwData;
			m_mouseDataBackPtr->deltaX += data.dwData;
			return;
		case DIMOFS_Y:
			//m_mouseDataBackPtr->yPos = data.dwData;
			m_mouseDataBackPtr->deltaY += data.dwData;
			return;
		case DIMOFS_Z:
			//m_mouseDataBackPtr->zPos = data.dwData;
			m_mouseDataBackPtr->deltaZ += data.dwData;
			return;
		case DIMOFS_BUTTON0:
			data.dwOfs = EIMOUSEBUTTON_0;
			break;
		case DIMOFS_BUTTON1:
			data.dwOfs = EIMOUSEBUTTON_1;
			break;
		case DIMOFS_BUTTON2:
			data.dwOfs = EIMOUSEBUTTON_2;
			break;
		case DIMOFS_BUTTON3:
			data.dwOfs = EIMOUSEBUTTON_3;
			break;
		case DIMOFS_BUTTON4:
			data.dwOfs = EIMOUSEBUTTON_4;
			break;
		case DIMOFS_BUTTON5:
			data.dwOfs = EIMOUSEBUTTON_5;
			break;
		case DIMOFS_BUTTON6:
			data.dwOfs = EIMOUSEBUTTON_6;
			break;
		case DIMOFS_BUTTON7:
			data.dwOfs = EIMOUSEBUTTON_7;
			break;
		}

		processKeyboardInput(data);
	}

	void DirectInputSystem::processKeyboardInput(DIDEVICEOBJECTDATA &data){
		std::map<DWORD,KeyDefinition>::iterator itr = m_keyTranslateMap.find(data.dwOfs);

		if(itr != m_keyTranslateMap.end()){
			// Button pressed
			if(data.dwData & 0x80){
				(*m_keyMapBackPtr)[itr->second].isPressed = true;
				(*m_keyMapBackPtr)[itr->second].wasPressed = false;

				// Inject into gui managers
				m_guiMgrMutex->lock();
				std::set<boost::shared_ptr<Graphics::GUIManager>>::iterator gui_itr = m_GUIManagers.begin();
				while(gui_itr != m_GUIManagers.end()){
					(*gui_itr)->injectKeyInput(itr->second,true);
					gui_itr++;
				}
				m_guiMgrMutex->unlock();
			}
			// Button released
			else if(!(data.dwData & 0x80)){
				if((*m_keyMapBackPtr)[itr->second].isPressed)
					(*m_keyMapBackPtr)[itr->second].wasPressed = true;
				(*m_keyMapBackPtr)[itr->second].isPressed = false;

				// Inject into gui managers
				m_guiMgrMutex->lock();
				std::set<boost::shared_ptr<Graphics::GUIManager>>::iterator gui_itr = m_GUIManagers.begin();
				while(gui_itr != m_GUIManagers.end()){
					(*gui_itr)->injectKeyInput(itr->second,false);
					gui_itr++;
				}
				m_guiMgrMutex->unlock();
			}
		}
	}

	void DirectInputSystem::cleanup(){
		if(m_keyboardDevice)
			m_keyboardDevice->Unacquire();
		if(m_keyboardDevice)
			m_keyboardDevice->Release();
		if(m_mouseDevice)
			m_mouseDevice->Unacquire();
		if(m_mouseDevice)
			m_mouseDevice->Release();
		if(m_inputDevice)
			m_inputDevice->Release();
	}
}